/*
 * Copyright 2019 The ZuSmart Project
 *  
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *  
 *      http://www.apache.org/licenses/LICENSE-2.0
 *  
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.zusmart.base.handler.support;

import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import com.zusmart.base.handler.Handler;
import com.zusmart.base.handler.HandlerChain;
import com.zusmart.base.handler.HandlerContext;
import com.zusmart.base.logging.Logger;
import com.zusmart.base.logging.LoggerFactory;
import com.zusmart.base.util.Assert;

/**
 * @author Administrator
 *
 */
public abstract class AbstractHandlerChain<T extends Handler, C extends HandlerContext<T, C, A>, A extends HandlerChain<T, C, A>> implements HandlerChain<T, C, A> {
	
	private static final Logger logger = LoggerFactory.getLogger(AbstractHandlerChain.class);
	
	protected final C head;
	protected final C foot;

	protected AbstractHandlerChain() {
		this.head = this.createFootHandlerContext();
		this.foot = this.createFootHandlerContext();
		this.head.setNext(this.foot);
		this.foot.setPrev(this.head);
	}

	@Override
	public void addFirst(String name, T handler) {
		final C context;
		synchronized (this) {
			context = this.createHandlerContext(name, handler);
			this.doAddFirst(context);
		}
	}

	@Override
	public void addLast(String name, T handler) {
		final C context;
		synchronized (this) {
			context = this.createHandlerContext(name, handler);
			this.doAddLast(context);
		}
	}

	@Override
	public void addBefore(String target, String name, T handler) {
		final C newContext;
		final C oldContext;
		synchronized (this) {
			oldContext = this.getContext(target);
			newContext = this.createHandlerContext(name, handler);
			Assert.isNull(oldContext, "old context not found [" + target + "]");
			this.doAddBefore(newContext, oldContext);
		}
	}

	@Override
	public void addAfter(String target, String name, T handler) {
		final C newContext;
		final C oldContext;
		synchronized (this) {
			oldContext = this.getContext(target);
			newContext = this.createHandlerContext(name, handler);
			Assert.isNull(oldContext, "old context not found [" + target + "]");
			this.doAddAfter(newContext, oldContext);
		}
	}

	@Override
	public void delHandler(String target) {
		C context = this.getContext(target);
		context.getPrev().setNext(context.getNext());
		context.getNext().setPrev(context.getPrev());
	}

	@Override
	public void setHandler(String target, String name, T handler) {
		C oldcontext = this.getContext(target);
		C newContext = this.createHandlerContext(name, handler);
		Assert.isNull(oldcontext, "old context not found [" + target + "]");
		newContext.setPrev(oldcontext.getPrev());
		newContext.setNext(oldcontext.getNext());
	}

	@Override
	public T getFirstHandler() {
		C context = this.getFirstHandlerContext();
		if (null != context) {
			return context.getHandler();
		}
		return null;
	}

	@Override
	public T getLastHandler() {
		C context = this.getLastHandlerContext();
		if (null != context) {
			return context.getHandler();
		}
		return null;
	}

	@Override
	public C getFirstHandlerContext() {
		C context = this.head.getNext();
		if (context == this.foot) {
			return null;
		}
		return this.head.getNext();
	}

	@Override
	public C getLastHandlerContext() {
		C context = this.foot.getPrev();
		if (context == this.head) {
			return null;
		}
		return this.foot.getPrev();
	}

	@Override
	public Set<C> getAllHandler() {
		Set<C> sets = new LinkedHashSet<C>();
		C context = this.head.getNext();
		for (;;) {
			if (this.foot == context) {
				return sets;
			}
			sets.add(context);
			context = context.getNext();
		}
	}
	
	@Override
	public void debugDump() {
		Set<String> handlers = new LinkedHashSet<String>();
		Iterator<C> iter = this.getAllHandler().iterator();
		int length = 0;
		while (iter.hasNext()) {
			String name = iter.next().getHandlerName();
			handlers.add(name);
			if (length < name.length()) {
				length = name.length();
			}
		}
		if (length % 2 == 0) {
			length++;
		}
		String next = "";
		for (int i = 0; i < length / 2; i++) {
			next += " ";
		}
		next += "↓";
		Iterator<String> nameIter = handlers.iterator();
		while (nameIter.hasNext()) {
			String name = nameIter.next();
			int len = (length - name.length()) / 2;
			String temp = "";
			while (temp.length() < len) {
				temp = " " + temp;
			}
			temp += name;
			logger.debug(temp);
			if (nameIter.hasNext()) {
				logger.debug(next);
			}
		}
	}

	private void doAddFirst(C newContext) {
		C nextContext = this.head.getNext();
		newContext.setPrev(this.head);
		newContext.setNext(nextContext);
		this.head.setNext(newContext);
		nextContext.setPrev(newContext);
	}

	private void doAddLast(C newContext) {
		C prevContext = this.foot.getPrev();
		newContext.setPrev(prevContext);
		newContext.setNext(this.foot);
		prevContext.setNext(newContext);
		this.foot.setPrev(newContext);
	}

	private void doAddBefore(C newContext, C oldContext) {
		newContext.setPrev(oldContext.getPrev());
		newContext.setNext(oldContext);
		oldContext.getPrev().setNext(newContext);
		oldContext.setPrev(newContext);
	}

	private void doAddAfter(C newContext, C oldContext) {
		newContext.setPrev(oldContext);
		newContext.setNext(oldContext.getNext());
		oldContext.getNext().setPrev(newContext);
		oldContext.setNext(newContext);
	}

	private C getContext(String name) {
		C context = this.head.getNext();
		while (context != this.foot) {
			if (context.getHandlerName().equals(name)) {
				return context;
			}
		}
		return null;
	}

	protected abstract C createHeadHandlerContext();

	protected abstract C createFootHandlerContext();

	protected abstract C createHandlerContext(String name, T handler);

}